# -*- coding: utf-8 -*-
import os
import re
import pwd
import sys
import time
import stat
import json
import base64
import threading
import subprocess
from tempfile import SpooledTemporaryFile


class Global:
    home_path, _script = os.path.split(sys.argv[0])
    task = _script.split('.')[0].upper()
    task_path = '%s/%s' % (home_path, task)
    download_path = '%s/__DOWNLOAD__' % home_path
    plots_path = '%s/PLOTS' % download_path
    plots_json = '%s/plots.json' % plots_path
    upload_path = '%s/__UPLOAD__' % home_path
    split_flag = '____BINGO_FILTER____'

    @classmethod
    def values_write(cls, data):
        with open('%s/__values__' % cls.task_path, 'w') as f:
            json.dump(data, f, ensure_ascii=False, indent=4)

    @classmethod
    def values_parse(cls, data):
        """ 有中文内容且python版本为py2时
        需要使用value.encode('utf-8')转码后再做字符串判断
        """
        string = base64.b64decode(data)
        return json.loads(string)

    @classmethod
    def _report(cls, status, progress, info, download):
        data = {
            "progress": progress,
            "status": status,
            "info": info,
            "download": download
        }
        with open('%s/__progress__' % cls.task_path, 'w') as f:
            json.dump(data, f, ensure_ascii=False, indent=4)

    @classmethod
    def report_info(cls, progress, info='', download=''):
        cls._report('RUNNING', progress, info, download)

    @classmethod
    def report_err(cls, progress, info='', download=''):
        cls._report('FAILED', progress, info, download)
        sys.exit(1)

    @classmethod
    def plots_init(cls):
        if not os.path.isfile(cls.plots_json):
            Common.execute_cmd("echo '{}' > %s && chmod 777 %s" % (cls.plots_json, cls.plots_json))

    @classmethod
    def plots_record(cls, csv, width=800, height=300, dpi=80, title='', label='', scale=None):
        with open(cls.plots_json, 'r') as f:
            data = json.load(f)
        if width > 800:
            width = 800
        if height > 300:
            height = 300
        if dpi > 100:
            dpi = 100
        if not title:
            title = os.path.basename(csv).replace('.csv', '')
        if not scale:
            scale = []
        data[csv] = {
            'width': width,
            'height': height,
            'dpi': dpi,
            'title': title,
            'label': label,
            'scale': scale
        }
        with open(cls.plots_json, 'w') as f:
            json.dump(data, f, ensure_ascii=False, indent=4)


class Human:

    @staticmethod
    def time(t=None, format=True):
        if format:
            return time.strftime("%Y/%m/%d,%H:%M:%S", time.localtime(t))
        return time.strftime("%Y%m%d%H%M%S", time.localtime())
    
    @staticmethod
    def size(s):
        if s < 1024:
            return '%sB' % s
        elif 1024 <= s < 1048576:
            return '%.1fKB' % (s / 1024.0)
        elif 1048576 <= s < 1073741824:
            return '%.2fMB' % (s / 1048576.0)
        return '%.2fGB' % (s / 1073741824.0)
    
    @staticmethod
    def mode(m):
        return oct(stat.S_IMODE(m))

    @staticmethod
    def user(u):
        try:
            return pwd.getpwuid(u).pw_name
        except Exception:
            return str(u)


class Common:

    @classmethod
    def write_to_file(cls, filename, info):
        try:
            with open(filename, 'w') as f:
                f.write(info)
                return True
        except Exception:
            return False

    @classmethod
    def cat_file(cls, filename):
        try:
            with open(filename, 'r') as f:
                return f.read()
        except Exception:
            return ''

    @classmethod
    def create_thread(cls, func, args=()):
        th = threading.Thread(target=func, args=args)
        th.setDaemon(True)
        th.start()
        return th

    @classmethod
    def execute_cmd(cls, cmd, msg=None):
        """ 这里不使用PIPE，原因是cmd中有 & 执行的命令或脚本时会造成subprocess读取PIPE卡死
        使用临时文件，接受输出和错误输出流 """
        def to_str(in_ss):
            if py_version == 2:
                return in_ss.strip()
            try:
                return str(in_ss, encoding='UTF-8').strip()
            except Exception:
                return str(in_ss, encoding='GBK', errors='ignore').strip()

        py_version = sys.version_info[0]
        with SpooledTemporaryFile(max_size=10240) as out:
            with SpooledTemporaryFile(max_size=10240) as err:
                p = subprocess.Popen([cmd], stdout=out.fileno(), stderr=err.fileno(), shell=True)
                p.wait()
                out.seek(0)
                err.seek(0)
                eno = p.returncode
                return not eno, to_str(out.read()), to_str(err.read())


def TableRender(lines):
    def impl():
        column_max_len = []
        line_list = lines.strip().split('\n')

        for line in line_list:
            for i, item in enumerate(line.split('|')):
                length = len(item)
                if len(column_max_len) <= i:
                    column_max_len.append(length)
                    continue
                if length > column_max_len[i]:
                    column_max_len[i] = length

        total_len = 3 * len(column_max_len) + 1
        for i in column_max_len:
            total_len += i

        infos = '-' * total_len
        for n, line in enumerate(line_list):
            infos = '%s\n' % infos
            for i, item in enumerate(line.split('|')):
                for_str = '{0:%s}' % column_max_len[i]
                new_item = for_str.format(item)
                infos = '%s | %s' % (infos, new_item) if i else '%s| %s' % (infos, new_item)
            infos = '%s |' % infos
            if n == 0:
                infos = '%s\n|%s|' % (infos, '-' * (total_len - 2))
        infos = '%s\n%s' % (infos, '-' * total_len)
        return infos

    try:
        return impl()
    except Exception:
        return lines


class PsUtil:
    """参考
    https://github.com/giampaolo/psutil/blob/master/psutil/_pslinux.py
    https://github.com/giampaolo/psutil/blob/master/psutil/__init__.py
    """

    PAGE_SIZE = 4096
    CLOCK_TICKS = os.sysconf("SC_CLK_TCK")
    BOOT_TIME = None

    @classmethod
    def pids(cls):
        return [int(x) for x in os.listdir('/proc') if x.isdigit()]

    @classmethod
    def init(cls):
        page_size = Common.execute_cmd('getconf PAGE_SIZE')[1]
        if page_size:
            cls.PAGE_SIZE = int(page_size)

        with open('/proc/stat', 'r') as f:
            for line in f:
                if line.startswith('btime'):
                    cls.BOOT_TIME = float(line.strip().split()[1])

    @classmethod
    def cpu_times(cls):
        ret = []
        with open('/proc/stat', 'r') as f:
            for line in f:
                if line.startswith('cpu'):
                    ret.append(list(map(lambda x: int(x), line.split()[1:8])))
        return ret

    @classmethod
    def cpu_info(cls):
        names, hzs = [], []
        with open('/proc/cpuinfo', 'r') as f:
            for line in f:
                line = line.strip().lower()
                if line.startswith('model name'):
                    names.append(line.split('\t:', 1)[1])
                elif line.startswith('cpu mhz'):
                    hzs.append(line.split('\t:', 1)[1])
        return names, hzs

    @classmethod
    def memory(cls):
        mems, temp = {}, {}
        with open('/proc/meminfo', 'r') as f:
            for line in f:
                fields = line.split()
                temp[fields[0]] = int(fields[1]) * 1024
        mems['total'] = total = temp['MemTotal:']
        mems['free'] = free = temp['MemFree:']
        mems['buffers'] = buffers = temp['Buffers:']
        mems['cached'] = cached = temp['Cached:']
        mems['swap_total'] = s_total = temp['SwapTotal:']
        mems['swap_free'] = s_free = temp['SwapFree:']
        used = total - free - cached - buffers
        if used < 0:
            used = total - free
        mems['used'] = used
        mems['swap_used'] = s_total - s_free
        mems['mem_percent'] = float(used) / total * 100
        mems['swap_percent'] = (float(s_total - s_free) / s_total) * 100 if s_total else 0
        return mems

    @classmethod
    def net_io(cls):
        nets = {}
        with open('/proc/net/dev', 'r') as f:
            lines = f.readlines()
        for line in lines[2:]:
            if line.find(":") < 0:
                continue
            dev = line.split(':')[0].strip()
            fields = line.split(':')[1].strip().split()
            net = {}
            net['bytes_recv'] = int(fields[0])
            net['packets_recv'] = int(fields[1])
            net['err_in'] = int(fields[2])
            net['drop_in'] = int(fields[3])
            net['bytes_sent'] = int(fields[8])
            net['packets_sent'] = int(fields[9])
            net['err_out'] = int(fields[10])
            net['drop_out'] = int(fields[11])
            nets[dev] = net
        return nets

    @classmethod
    def diskstats(cls):
        def line_to_dict(line):
            """
            line:
                major, minor, dev, r_ios, r_merges, r_sec, r_ticks, w_ios,
                w_merges, w_sec, w_ticks, ios_pgr, tot_ticks, rq_ticks
            """
            out = {}
            line_list = line.split()[2:14]
            tag_list = ['dev', 'r_ios', 'r_merges', 'r_sec', 'r_ticks', 'w_ios', 'w_merges', 'w_sec',
                        'w_ticks', 'ios_pgr', 'tot_ticks', 'rq_ticks']
            for i, tag in enumerate(tag_list):
                out[tag] = line_list[i] if i == 0 else int(line_list[i])
            out['ts'] = time.time()
            return out

        with open("/proc/partitions", 'r') as f:
            lines = f.readlines()[2:]
        partitions = set([line.split()[-1] for line in lines if not line.strip()[-1].isdigit()])

        with open("/proc/diskstats", 'r') as f:
            lines = f.readlines()
        stats = {}
        for stat in [line_to_dict(line) for line in lines]:
            if stat['dev'] in partitions:
                stats[stat['dev']] = stat
        return stats

    class Process(object):
        def __init__(self, pid, only_cpu=False):
            self.pid = pid
            self.stat_info = self._parse_stat_file()
            if not only_cpu:
                self.status_info = self._read_status_file()
                self.mem_info = self._read_mem_file()

        def _parse_stat_file(self):
            with open("/proc/%s/stat" % self.pid) as f:
                data = f.read()
            rpar = data.rfind(')')
            name = data[data.find('(') + 1:rpar]
            fields = data[rpar + 2:].split()

            ret = {}
            try:
                ret['name'] = name
                ret['status'] = fields[0]
                ret['ppid'] = fields[1]
                #ret['ttynr'] = fields[4]
                ret['utime'] = fields[11]
                ret['stime'] = fields[12]
                #ret['children_utime'] = fields[13]
                #ret['children_stime'] = fields[14]
                ret['create_time'] = fields[19]
                #ret['cpu_num'] = fields[36]
                #ret['blkio_ticks'] = fields[39]
            except:
                pass
            return ret

        def _read_status_file(self):
            with open("/proc/%s/status" % self.pid, 'r') as f:
                return f.read()

        def _read_mem_file(self):
            ret = {}
            with open("/proc/%s/statm" % self.pid, 'r') as f:
                vms, rss = f.readline().split()[:2]
            ret['vms'] = int(vms) * PsUtil.PAGE_SIZE
            ret['rss'] = int(rss) * PsUtil.PAGE_SIZE
            return ret

        def name(self):
            # 进程名称长度大于15个字符会被截断, 此处暂时不考虑从cmdline中获取 #
            return self.stat_info['name']

        def username(self):
            _uids_re=re.compile(r'Uid:\t(\d+)\t(\d+)\t(\d+)')
            real, _, _ = _uids_re.findall(self.status_info)[0]
            try:
                return pwd.getpwuid(int(real)).pw_name
            except Exception:
                return str(real)

        def ppid(self):
            return int(self.stat_info['ppid'])

        def num_threads(self):
            _num_threads_re = re.compile(r'Threads:\t(\d+)')
            return int(_num_threads_re.findall(self.status_info)[0])

        def memory(self):
            return self.mem_info['rss']

        def create_time(self):
            return Human.time((float(self.stat_info['create_time']) / PsUtil.CLOCK_TICKS) + PsUtil.BOOT_TIME)

        def cpu_times(self):
            user = float(self.stat_info['utime']) / PsUtil.CLOCK_TICKS
            sys = float(self.stat_info['stime']) / PsUtil.CLOCK_TICKS
            return user, sys

        def mem_percent(self, total):
            return (float(self.mem_info['rss']) / total) * 100


class JsonParser:
    @classmethod
    def load(cls, json_path):
        with open(json_path, 'r', encoding='UTF-8') as f:
            return json.load(f)

    @classmethod
    def loads(cls, data):
        return json.loads(data)

    @classmethod
    def write(cls, json_path, data):
        with open(json_path, 'w', encoding='UTF-8') as f:
            json.dump(data, f, ensure_ascii=False, indent=4)


if __name__ == '__main__':
    func = sys.argv[1]
    if func == 'TableRender':
        print(TableRender(sys.argv[2]))

